package com.xwj.exception.solution_3.handler;

import com.xwj.common.util.R;
import com.xwj.common.util.enums.CommonErrorEnum;
import com.xwj.exception.common.exceptions.BusinessException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.ConversionNotSupportedException;
import org.springframework.beans.TypeMismatchException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.DataBinder;
import org.springframework.validation.FieldError;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingPathVariableException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.context.request.async.AsyncRequestTimeoutException;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.multipart.support.MissingServletRequestPartException;
import org.springframework.web.servlet.NoHandlerFoundException;

import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * @Author: xiaowajiang
 * @Date: 2022-04-05 19:59
 * @Description: @ControllerAdvice 和 @ExceptionHandler 注解实现统一异常处理，
 *
 * 在当前这个统一异常处理器中，返回异常时，我同时设置了相应的 HTTP 错误状态，以便客户端决定下一步该如何处理。大家可以根据自己的需求进行调整。
 **/
@Slf4j
//@RestControllerAdvice
@RestControllerAdvice("com.xwj.exception.solution_3.demo.controller") // 为了方便多方案测试增加了包扫描，注意：增加了包扫描后 ServletException 异常捕获会受影响
public class UnifiedExceptionHandler {

    /**
     * 未知异常处理
     * @param t
     * @return
     */
    @ExceptionHandler(value = Throwable.class)
    public ResponseEntity handleException(Throwable t) {
        log.error("未知异常，{}，异常类型：{}", t.getMessage(), t.getClass(), t);
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR.value()).body(R.error());
    }

    /**
     * 业务异常 - 请求无法完成
     * @param e 异常
     * @return 异常结果
     */
    @ExceptionHandler(value = BusinessException.class)
    public ResponseEntity handleBaseException(BusinessException e) {
        log.error("业务处理异常，{}", e.getMessage(), e);
        return ResponseEntity.status(HttpStatus.BAD_REQUEST.value())
                .body(R.error(e.getCode(), e.getMessage()));
    }

    /**
     * 进入 Controller 前的相关异常
     * 注意 @RestControllerAdvice 中不要使用包扫描，否则无法捕获 ServletException 异常
     * @param e 异常
     * @return 异常结果
     */
    @ExceptionHandler({
            NoHandlerFoundException.class,
            MethodArgumentTypeMismatchException.class,
            HttpRequestMethodNotSupportedException.class,
            HttpMediaTypeNotSupportedException.class,
            HttpMediaTypeNotAcceptableException.class,
            MissingPathVariableException.class,
            MissingServletRequestParameterException.class,
            TypeMismatchException.class,
            HttpMessageNotReadableException.class,
            HttpMessageNotWritableException.class,
            ServletRequestBindingException.class,
            ConversionNotSupportedException.class,
            MissingServletRequestPartException.class,
            AsyncRequestTimeoutException.class
    })
    public ResponseEntity handleServletException(Exception e) {
        log.error("网络请求异常，{}", e.getMessage(), e);
        CommonErrorEnum errorEnum = CommonErrorEnum.valueOf(e.getClass().getSimpleName());
        return ResponseEntity.status(errorEnum.getHttp())
                .body(R.error(errorEnum.getCode(), errorEnum.getMessage()));
    }

    /**
     * 参数校验(Valid)异常
     * @param e
     * @return
     */
    @ExceptionHandler(value = {MethodArgumentNotValidException.class})
    public ResponseEntity handleValidException(MethodArgumentNotValidException e) {
        log.error("数据校验异常，{}，异常类型：{}", e.getMessage(), e.getClass());
        BindingResult bindingResult = e.getBindingResult();
        Map<String, String> errorMap = getErrorMap(bindingResult);
        CommonErrorEnum errorEnum = CommonErrorEnum.PARAM_VALID_ERROR;
        return ResponseEntity.status(errorEnum.getHttp())
                .body(R.error(errorEnum.getCode(), errorEnum.getMessage()).put("data", errorMap));
    }

    /**
     * 参数绑定异常
     * @param e
     * @return
     */
    @ExceptionHandler(value = {BindException.class})
    public ResponseEntity handleBindException(BindException e) {
        log.error("数据校验异常，{}，异常类型：{}", e.getMessage(), e.getClass());
        BindingResult bindingResult = e.getBindingResult();
        Map<String, String> errorMap = getErrorMap(bindingResult);
        CommonErrorEnum errorEnum = CommonErrorEnum.PARAM_BIND_ERROR;
        return ResponseEntity.status(HttpStatus.BAD_REQUEST.value())
                .body(R.error(errorEnum.getCode(), errorEnum.getMessage()).put("data", errorMap));
    }

    /**
     * 参数约束检查异常
     * @param e
     * @return
     */
    @ExceptionHandler(value = {ConstraintViolationException.class})
    public ResponseEntity handleConstraintViolationException(ConstraintViolationException e) {
        log.error("数据校验异常，{}，异常类型：{}", e.getMessage(), e.getClass());
        List<String> violations = e.getConstraintViolations().stream()
                .map(ConstraintViolation::getMessage).collect(Collectors.toList());
        String error = violations.get(0);
        CommonErrorEnum errorEnum = CommonErrorEnum.CONSTRAINT_VIOLATION_ERROR;
        return ResponseEntity.status(HttpStatus.BAD_REQUEST.value())
                .body(R.error(errorEnum.getCode(), errorEnum.getMessage()).put("data", error));
    }

    /**
     * 获取校验失败的结果
     * @param result
     * @return
     */
    private Map<String, String> getErrorMap(BindingResult result) {
        return result.getFieldErrors().stream().collect(
                Collectors.toMap(FieldError::getField, FieldError::getDefaultMessage, (k1, k2) -> k1)
        );
    }

    /**
     * DataBinder 数据绑定访问器，集合参数校验时需要这个数据绑定
     * @param dataBinder
     */
    @InitBinder
    private void activateDirectFieldAccess(DataBinder dataBinder) {
        dataBinder.initDirectFieldAccess();
    }
}
